package cn.okcode.slowjson.parser;

import cn.okcode.slowjson.ast.*;

import java.util.Map;

/**
 * 格式化json字符串
 *
 * @author pyk13
 */
public final class FormatVisitorImpl implements Visitor {

    private final StringBuilder out = new StringBuilder();

    public static final String INDENT_PADDING = " ";

    private final boolean format;
    private final boolean bRemoveQuote;

    private int scope = 0;

    private void enterScope() {
        scope++;
    }

    private void exitScope() {
        scope--;
    }

    /**
     * 构造格式化访问者对象
     *
     * @param format 是否美化格式
     */
    public FormatVisitorImpl(boolean format) {
        this(format, false);
    }

    public FormatVisitorImpl(boolean format, boolean bRemoveQuote) {
        this.format = format;
        this.bRemoveQuote = bRemoveQuote;
    }

    //记录最后一次换行符的位置
    private int lastLineSeparatorIndex;

    private void appendLineSeparator() {
        if (format && lastLineSeparatorIndex != out.length()) {
            out.append(System.lineSeparator());
            lastLineSeparatorIndex = out.length();
        }
    }

    private void padEmpty() {
        if (!format) {
            return;
        }
        int len = scope * 4;

        int existLen = getLastCharPosition();
        if (existLen >= len) {
            return;
        }
        for (int i = 0; i < len; i++) {
            out.append(INDENT_PADDING);
        }
    }

    /**
     * 获取最后换行符后一个字符到当前字符的长度
     *
     * @return 最后一个字符所在行的相对位置
     */
    private int getLastCharPosition() {
        if (lastLineSeparatorIndex == -1) {
            return out.length();
        }
        return out.length() - lastLineSeparatorIndex - 1;
    }

    public String print() {
        return out.toString();
    }

    @Override
    public void visit(ArrayNode node) {
        padEmpty();
        out.append("[");
        enterScope();
        if (!node.getElements().isEmpty()) {
            appendLineSeparator();
            int index = 0;
            for (AbstractNode<?> childNode : node.getElements()) {
                if (index > 0) {
                    out.append(",");
                    appendLineSeparator();
                }
                index++;
                childNode.accept(this);
            }
            appendLineSeparator();
        }
        exitScope();
        padEmpty();
        out.append("]");
    }

    @Override
    public void visit(BoolNode node) {
        padEmpty();
        out.append(node.getValue());
    }

    @Override
    public void visit(NullNode node) {
        padEmpty();
        out.append(node.getValue());
    }

    @Override
    public void visit(NumberNode node) {
        padEmpty();
        String strValue = node.stringValue();
        String[] arr = strValue.split("\\.");
        if (arr.length == 2 && "0".equals(arr[1])) {
            strValue = arr[0];
        }
        out.append(strValue);
    }

    @Override
    public void visit(ObjectNode node) {
        padEmpty();
        out.append("{");
        enterScope();
        if (!node.getProperties().isEmpty()) {
            appendLineSeparator();
            int index = 0;
            for (Map.Entry<PropertyNode, AbstractNode<?>> entry : node.getProperties().entrySet()) {
                AbstractNode<?> childNode = entry.getValue();
                padEmpty();

                if (bRemoveQuote) {
                    out.append(entry.getKey().getValue());
                } else {
                    out.append(entry.getKey().stringValue());
                }

                if (format) {
                    out.append(": ");
                } else {
                    out.append(":");
                }
                childNode.accept(this);

                if (index + 1 < node.getProperties().size()) {
                    out.append(',');
                }

                appendLineSeparator();
                index++;
            }
            exitScope();
            padEmpty();
        }
        out.append("}");
    }

    @Override
    public void visit(StringNode node) {
        padEmpty();
        out.append(node.stringValue());
    }

    @Override
    public void visit(PropertyNode node) {
        padEmpty();
        out.append(node.stringValue());
    }
}
